Compound Component Pattern을 이용한 컴포넌트 추상화

마지막 수정일: 2025. 05. 09.

문제상황

다음과 같은 search modal을 과거에 만든적이 있었다.


코드는 다음과 같다.

TYPESCRIPT
import { searchDocuments } from "@/api/meilisearch";
import { FileSvg, MagnifyingGlassSvg } from "./icons";
import { useState } from "react";
import Link from "next/link";
import { MDData } from "@/lib/types";

interface SearchResult extends MDData {
  _formatted: MDData;
}

export default function SearchModal({
  setIsOpen,
}: {
  setIsOpen: (isOpen: boolean) => void;
}) {
  const [searchResults, setSearchResults] = useState<SearchResult[]>([]);

  async function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
    const searchQuery = event.target.value;
    const results = await searchDocuments(searchQuery);
    setSearchResults(results.hits as SearchResult[]);
  }

  return (
    <div onClick={() => setIsOpen(false)}>
      <Container onClick={(e) => e.stopPropagation()}>
        <Input
          type="text"
          autoFocus
          placeholder="Search docs..."
          onChange={handleChange}
        />

        <div className="flex flex-col gap-2.5">
          {searchResults.length > 0 ? (
            searchResults.map((result) => (
              <SearchResultItem
                key={result.id}
                setIsOpen={setIsOpen}
                result={result}
              />
            ))
          ) : (
            <div className="flex items-center justify-center h-[100px]">
              <span className="text-text text-lg">No results
              </span>
            </div>
          )}
        </div>
      </Container>
    </div>
  );
}

굉장히 단순화한 버전이지만 응집도, 결합도, 추상화면에서 모두 문제가 존재하는 코드다. 심지어 여기에 키보드 입력으로 item을 선택하고 만들고 싶은데 문제가 더욱 심해질 듯 하였다.

응집도 : 컴포넌트의 구분 없이 한 컴포넌트에서 UI/상태관리/API를 모두 처리했다.
결합도 : setIsOpen의 depth가 높았으며 selected값이 들어올 경우 같이 증가할 것이 뻔했다.
추상화 : UI/API 로직이 같이 있어 SearchModal 자체에서 처리가 들어갔다.

해결방법

Compound Component를 사용해 문제를 해결하였다.

data, isopen, setisopen, query, onChange(setquery)

SearchModalFrame(isopen, setisopen) - 실제 api와 상태 작업(data)

data api로 받기
context 만들어서 provider로 제공 data, query "", onchange

SearchModal -

Input - onchange

Results

Item - data, query, setisopen

Empty